home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-15 / phbench.zip / ARTICLE < prev    next >
Text File  |  1993-01-04  |  15KB  |  399 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7. [The following article appeared in  "C Users Journal" May 1988.
  8.  It describes the purpose and use of the enclosed benchmarks. ]
  9.  
  10.  
  11. SIMPLE BENCHMARKS FOR C COMPILERS
  12.  
  13. by Thomas Plum
  14.  
  15. Dr.Plum is the author of several books on  C,  including  Efficient  C  (co-
  16. authored  with  Jim  Brodie).  He is Vice-Chair of the ANSI X3J11 Committee,
  17. and Chairman of Plum Hall Inc, which offers introductory and  advanced  sem-
  18. inars on C.
  19.  
  20. Copyright (c) 1988, Plum Hall Inc
  21.  
  22.  
  23. We are placing into the public domain some simple  benchmarks  with  several
  24. appealing properties:
  25.  
  26.     They are short enough to type while browsing at trade shows.
  27.  
  28.     They are protected against overly-aggressive compiler optimizations.
  29.  
  30.     They reflect empirically-observed operator frequencies in C programs.
  31.  
  32.     They give a C programmer information directly relevant to programming.
  33.  
  34. In Efficient C, Jim Brodie and I described how useful it can be for  a  pro-
  35. grammer  to have a general idea of how many microseconds it takes to execute
  36. the "average operator" on   register  int's,  on   auto  short's,  on   auto
  37. long's,  and  on  double  data, as well as the time for an integer multiply,
  38. and the time to call-and-return from a function.  These six numbers allow  a
  39. programmer  to  make  very good first-order estimates of the CPU time that a
  40. particular algorithm will take.
  41.  
  42. The  following  easily-typed  benchmark  programs  determine   these   times
  43. directly.   The  first  one  is  benchreg.c  ("benchmark for register opera-
  44. tors"):
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.                                    - 1 -
  67.  
  68.  
  69.  
  70.  
  71.  
  72.                                    - 2 -
  73.  
  74.  
  75.     1   /* benchreg - benchmark for  register  integers
  76.     2    * Thomas Plum, Plum Hall Inc, 609-927-3770
  77.     3    * If machine traps overflow, use an  unsigned  type
  78.     4    * Let  T  be the execution time in milliseconds
  79.     5    * Then  average time per operator  =  T/major  usec
  80.     6    * (Because the inner loop has exactly 1000 operations)
  81.     7    */
  82.     8   #define STOR_CL register
  83.     9   #define TYPE int
  84.    10   #include <stdio.h>
  85.    11   main(ac, av)
  86.    12           int ac;
  87.    13           char *av[];
  88.    14           {
  89.    15           STOR_CL TYPE a, b, c;
  90.    16           long d, major, atol();
  91.    17           static TYPE m[10] = {0};
  92.    18
  93.    19           major = atol(av[1]);
  94.    20           printf("executing %ld iterations0, major);
  95.    21           a = b = (av[1][0] - '0');
  96.    22           for (d = 1; d <= major; ++d)
  97.    23                   {
  98.    24                   /* inner loop executes 1000 selected operations */
  99.    25                   for (c = 1; c <= 40; ++c)
  100.    26                           {
  101.    27                           a = a + b + c;
  102.    28                           b = a >> 1;
  103.    29                           a = b % 10;
  104.    30                           m[a] = a;
  105.    31                           b = m[a] - b - c;
  106.    32                           a = b == c;
  107.    33                           b = a | c;
  108.    34                           a = !b;
  109.    35                           b = a + c;
  110.    36                           a = b > c;
  111.    37                           }
  112.    38                   }
  113.    39           printf("a=%d0, a);
  114.    40           }
  115.  
  116. If you enter this and compile it to produce an executable program,  you  can
  117. invoke it with one argument, the number of iterations for the major loop:
  118.  
  119.     benchreg  10000
  120.  
  121. If this execution takes 16 seconds, this means that  the  average   register
  122. operation  takes  1.6  microseconds  (16,000  milliseconds divided by 10,000
  123. iterations of the major loop).
  124.  
  125. Let us examine the program  in  detail.   Lines  8  and  9  define   STOR_CL
  126. ("storage  class")  and  TYPE  to be register  and  int .  Thus, on line 15,
  127. three variables ( a , b , and  c ) are declared to be of this storage  class
  128. and type.  At line 16, the major loop control variables are  long  integers,
  129. but they are touched only one one-thousandth as  often  as  the  inner  loop
  130.  
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137.  
  138.                                    - 3 -
  139.  
  140.  
  141. variables, so they have little effect upon the timings.   We  are  declaring
  142. the   atol   function to return a  long  integer; it would otherwise default
  143. to an  int  return.  (If we were using a compiler based upon draft  ANSI  C,
  144. we  could   #include  <stdlib.h>  to get the declaration of  atol , but this
  145. would limit the applicability of the benchmarks.  This simple declaration is
  146. all that even an ANSI compiler would need.)
  147.  
  148. At line 19, we set the  major  loop variable to the number given on the com-
  149. mand line, and at line 20, we confirm it to the output.
  150.  
  151. Line 21 is crucial to preventing some overly aggressive optimizations.  Ear-
  152. lier  versions  of  these benchmarks had simply initialized  a  and  b to 1,
  153. but this allows a compiler to forward-propagate a known constant value.  The
  154. expression   av[1][0]   gives  the first digit-character of the command-line
  155. argument; subtracting  '0'  produces a digit between 0  and  9.   (Yes,  the
  156. latest  ANSI draft now guarantees that the digit characters are a contiguous
  157. sequence in any environment.)
  158.  
  159. Line 22 simply executes the major loop the number  of  times  given  by  the
  160. variable   major  .   Line  25  repeats the inner loop 40 times, and with 25
  161. operators in that loop, this produces 1000 operators.  (Actually  there  are
  162. 1003, because of the initialization and the extra increment and test at loop
  163. completion.  The discrepancy is well within acceptable tolerances.)
  164.  
  165. Within the inner loop, 40% of the operators are assignments, in keeping with
  166. the  percentages  reported  in  the  original  Drhystone work.  Of the other
  167. operators, the most frequent are plus and minus.  The sequence of operations
  168. is  carefully  chosen to ensure that a very aggressive optimizer cannot find
  169. any useless code sections; each result depends  functionally  upon  previous
  170. results.
  171.  
  172. Finally, the printout at line 39  is  also  important  to  preventing  over-
  173. optimization.   If  the  compiler  could notice that we did nothing with the
  174. computed result, it could discard all  the  operations  that  produced  that
  175. result.
  176.  
  177. We have completed our perusal of the first benchmark program,  benchreg.c  .
  178. The second program (  benchsho.c , for  short's) is derived from  benchreg.c
  179. by changing lines 8 and 9:  STOR_CL  becomes   auto  ,  and   TYPE   becomes
  180. short .  The program is otherwise unchanged.
  181.  
  182. The third program (  benchlng.c  ,  for   long's)  is  obtained  by  leaving
  183. STOR_CL  as  auto and changing  TYPE  to  long .
  184.  
  185. To make the fourth program ( benchmul.c , for multiplies) we set   TYPE   to
  186. int , and change lines 27 through 36 to one source line which does 25 multi-
  187. plies:
  188.  
  189.     a = 3 *a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a; /* 25 * */
  190.  
  191. The fifth program ( benchfn.c , for  functions)  is  a  major  rewrite.   We
  192. arrange  a series of function definitions for  f3 ,  f2 ,  f1 , and  f0 such
  193. that each call to function  f0  generates exactly 1000 function-call  opera-
  194. tions.   In case the compiler has an aggressive optimizer, move the function
  195. f3  to a separate source file, so that the compiler cannot see  how  useless
  196.  
  197.  
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.                                    - 4 -
  205.  
  206.  
  207. it is.  The global variable  dummy  will make the compiler  think  that   f3
  208. might be up to something useful.  Here, then, is the  benchfn.c  function:
  209.  
  210.     1   /* benchfn - benchmark for function calls
  211.     2    * Thomas Plum, Plum Hall Inc, 609-927-3770
  212.     3    * Let  T  be the execution time in milliseconds
  213.     4    * Then  average time per operator  =  T/major  usec
  214.     5    * (Because the inner loop has exactly 1000 operations)
  215.     6    */
  216.     7   #include <stdio.h>
  217.     8   int dummy = 0;
  218.     9
  219.    10   /* f3 - lowest level function
  220.    11    * Put this in separate source file if compiler detects and
  221.    12    * optimizes useless code
  222.    13    */
  223.    14   f3() { }
  224.    15
  225.    16   f2() { f3();f3();f3();f3();f3();f3();f3();f3();f3();f3();} /* 10 */
  226.    17   f1() { f2();f2();f2();f2();f2();f2();f2();f2();f2();f2();} /* 10 */
  227.    18   f0() { f1();f1();f1();f1();f1();f1();f1();f1();f1();} /* 9 */
  228.    19
  229.    20   main(ac, av)
  230.    21           int ac;
  231.    22           char *av[];
  232.    23           {
  233.    24           long d, major, atol();
  234.    25
  235.    26           major = atol(av[1]);
  236.    27           printf("executing %ld iterations0, major);
  237.    28           for (d = 1; d <= major; ++d)
  238.    29                   f0(); /* executes 1000 calls */
  239.    30           printf("dummy=%d0, dummy);
  240.    31           }
  241.  
  242. The sixth program ( benchdblc. , for  double's ) is derived from  benchlng.c
  243. by  changing  STOR_CL  to  auto , TYPE  to  double , and replacing the inner
  244. loop body with this slightly different version:
  245.  
  246.     a = a + b + c;
  247.     b = a * 2;
  248.     a = b / 10;
  249.     a = -a;
  250.     b = -a - b - c;
  251.     a = b == c;
  252.     b = a + c;
  253.     a = !b;
  254.     b = a + c;
  255.     a = b > c;
  256.  
  257. These changes are necessary because floating-point operands are not  allowed
  258. for  the  shift, remainder, and bitwise operators, and because the subscript
  259. operator does not really exercise  the  floating-point  instructions.   This
  260. revised  inner  loop  still  gives us a representative mix of typical opera-
  261. tions.
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.                                    - 5 -
  271.  
  272.  
  273. This, then, completes our collection of six benchmark programs.  After  they
  274. are  compiled to produce executable programs, the next question is "How do I
  275. time the execution?"
  276.  
  277. On UNIX systems, the timing is easy -- just run the  time  command:
  278.  
  279.     $ time benchreg 10000
  280.  
  281. The sum of the "user" and "system" times will give the CPU time used by  the
  282. program.
  283.  
  284. More accurately, we could time the execution of zero  iterations,  and  sub-
  285. tract that time from the time for the measured number of iterations.
  286.  
  287. On MS-DOS systems, timings can be obtained, but with greater difficulty.  If
  288. we  create  a  file  named   CR-LF   which  contains  just  one  newline (or
  289. "carriage-return-newline" in DOS parlance), we could time our program with a
  290. "batch" file such as this:
  291.  
  292.     time <cr-lf
  293.     benchreg 0
  294.     time <cr-lf
  295.     benchreg 10000
  296.     time <cr-lf
  297.  
  298. We must then take times that are expressed in minutes-and-seconds  and  pro-
  299. duce differences expressed in seconds.
  300.  
  301. With whichever method, we eventually produce six numbers that are character-
  302. istic of a particular environment (a specific compiler supporting a specific
  303. machine).
  304.  
  305. [NOTE: Since this article appeared, I have added a driver program,  benches.c.
  306. In an ANSI environment with the  clock  function, it will run all the tests
  307. and report the results, eliminating the need for manual computations.]
  308.  
  309. Here are some examples of timing  results  that  have  been  obtained  on  a
  310. variety of minicomputer and workstation environments:
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327.  
  328.  
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.                                    - 6 -
  337.  
  338.  
  339. Machine/compiler    register   auto       auto     int        func    auto
  340.                     int        short      long     multiply   call    dbl
  341.  
  342. AT&T 3B2/05 (-O)      1.36      3.87      2.62     15.4       7.7     22.5
  343. AT&T 3B2/05 (no -O)   1.78      4.66      2.75     16.2       9.3     22.5
  344. AT&T 3B2/400 (-O)     1.09      1.36      1.10     16.2      10.0(?)  91.4
  345. AT&T 3B2/400 (no -O)  1.14      2.61      2.36     17.3      11.3     91.1
  346. Apollo DN330 (-O)     1.36       .78      1.36     10.17      3.57
  347. Apollo DN330 (no -O)  1.54      1.28      1.54     11.30      3.64
  348. Apollo DN580 (-O)     1.03       .59      1.03      7.67      2.72
  349. Apollo DN580 (no -O)  1.18       .97      1.18      8.48      2.77
  350. Apollo DN660 (-O)     5.88      1.24      5.88     21.86      4.26
  351. Apollo DN660 (no -O)  5.93      1.52      5.93     21.93      4.29
  352. Cray X-MP (no vectors) .0567     .0656     .0822     .366      .821     .082
  353. Masscomp 5500         3.18      2.7       4.9      30.8       7.3
  354. Masscomp 5600 (-O)     .45       .61       .46      2.83      1.04
  355. Masscomp 5600 (no -O)  .46       .78       .64      2.99      1.76
  356. Pyramid 90X (-O)       .85      1.04       .86      3.64      1.9      2.37
  357. Pyramid 90X (no -O)    .86      1.01       .86      3.65      1.8      2.34
  358. Sequent (-O)          1.39      2.99      2.53      9.90      9.3
  359. Sequent (no -O)       1.50      3.25      2.83      9.95     13.2
  360. Sun 3/260HM (-O)       .31       .48       .47      1.98      1.16
  361. Sun 3/260HM (no -O)    .36       .58       .57      1.99      1.62
  362. Sun 3/75M (-O)         .47       .77       .76      3.00      2.12
  363. Sun 3/75M (no -O)      .53       .95       .94      3.01      2.73
  364. Sun 3/75M(4.2, -O)     .50       .81       .83      2.85      1.5     20.7
  365. Sun 3/75M(4.2, no -O)  .54      1.00      1.01      2.97      2.7     21.1
  366. Sun 3/75M(VM, -O)      .46       .77       .75      2.96      2.1     20.8
  367. Sun 3/75M(VM, no -O)   .52       .96       .93      2.97      2.7     21.1
  368. VAX 11/730 (-O)       4.00      9.80      6.20     16.2      42.8     12.4
  369. VAX 11/730 (no -O)    4.73     10.2       7.45     16.57     51.5     17.0
  370. VAX 11/780 (-O)       1.21      2.43      1.67      2.76     15.0      2.95
  371. VAX 11/780 (BSD 4.2)  1.38      2.42      1.96      2.92     17.2
  372. VAX 11/780 (UNIX 5.2) 1.24      2.48      1.79      2.72     15.7      3.89
  373. VAX 11/780 (no -O)    1.29      2.51      1.85      2.70     16.7      3.89
  374. VAX 11/785 (-O)        .93      1.85      1.32      5.00     13.9     47.5
  375. VAX 11/785 (no -O)    1.01      1.96      1.44      5.08     14.2      5.42
  376. VAX 8650(UNIX -O)      .236      .484      .298      .589     2.63      .578
  377. VAX 8650(UNIX no -O)   .258      .482      .316      .574     3.06      .791
  378. VAX 8650(Ultrix -O)    .23       .40       .29       .53      2.4       .56
  379. VAX 8650(Ultrix no -O) .26       .41       .34       .56      2.8       .77
  380.  
  381. Notice that some of these timings were run before the   benchdbl   benchmark
  382. had  been  written.  There are no examples of the popular PC environments in
  383. this table.  If interested readers wish to run these benchmarks on their own
  384. environments, I will endeavor to present these results in a future article.
  385.  
  386. Processor speeds are sometimes described in "MIPS" (millions of instructions
  387. per  second);  using  a value such as the number of  register  operators per
  388. second in C might give rise to a "MOPS" measurement of more use  to  C  pro-
  389. grammers.   Those of us who have tried these benchmarks have appreciated the
  390. intuitive grasp that they give of the speed of  current  machines  and  com-
  391. pilers.  I hope that you too will find them of interest.
  392.  
  393.  
  394.  
  395.  
  396.  
  397.  
  398.  
  399.